package roles

import (
	"context"
	"fmt"
	"sync"
	"time"

	protoactor "github.com/asynkron/protoactor-go/actor"
	"github.com/google/uuid"

	"xagent/internal/actor"
	"xagent/internal/knowledge"
	"xagent/internal/memory"
	"xagent/internal/providers"
	proto "xagent/proto/v1"
)

// ActorRole combines the capabilities of both Actor and Role
type ActorRole struct {
	*actor.BaseActor
	*BaseRole
	thoughtChain *actor.ThoughtChain
	supervisor   *protoactor.PID
	mutex        sync.RWMutex
}

// NewActorRole creates a new ActorRole instance
func NewActorRole(name, role, profile, description string) *ActorRole {
	baseActor := actor.NewBaseActor(name, role, profile)
	baseRole := NewBaseRole(uuid.NewString(), name, role, description)

	return &ActorRole{
		BaseActor: baseActor,
		BaseRole:  baseRole,
		thoughtChain: &actor.ThoughtChain{
			Thoughts: make([]actor.Thought, 0),
		},
		mutex: sync.RWMutex{},
	}
}

// Receive handles incoming messages
func (ar *ActorRole) Receive(context protoactor.Context) {
	ar.BaseActor.Receive(context)

	switch msg := context.Message().(type) {
	case *Message:
		ar.handleRoleMessage(context, msg)
	case *proto.Task:
		ar.handleTask(context, msg)
	}
}

// handleRoleMessage processes messages specific to the role
func (ar *ActorRole) handleRoleMessage(ctx protoactor.Context, msg *Message) {
	ar.mutex.Lock()
	ar.BaseRole.SetState(StateExecuting)
	ar.mutex.Unlock()

	defer func() {
		ar.mutex.Lock()
		ar.BaseRole.SetState(StateIdle)
		ar.mutex.Unlock()
	}()

	// Process the message using role's capabilities
	response, err := ar.ProcessTask(context.Background(), msg)
	if err != nil {
		ar.sendError(ctx, err)
		return
	}

	// Convert response to AgentMessage
	agentMsg := &proto.AgentMessage{
		Id:     response.ID,
		Type:   proto.MessageType_MESSAGE_TYPE_RESPONSE,
		Sender: ar.BaseRole.GetRole(),
		Content: &proto.AgentMessage_Message{
			Message: response.Content,
		},
	}

	ctx.Send(ctx.Parent(), agentMsg)
}

// handleTask processes tasks using both actor and role capabilities
func (ar *ActorRole) handleTask(ctx protoactor.Context, task *proto.Task) {
	ar.mutex.Lock()
	ar.BaseRole.SetState(StateExecuting)
	ar.mutex.Unlock()

	defer func() {
		ar.mutex.Lock()
		ar.BaseRole.SetState(StateIdle)
		ar.mutex.Unlock()
	}()

	// Convert task to role message
	msg := &Message{
		ID:      task.Id,
		Type:    task.Name,
		Content: task.Description,
		Metadata: map[string]string{
			"status": task.Status.String(),
		},
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	// Process using role capabilities
	response, err := ar.ProcessTask(context.Background(), msg)
	if err != nil {
		ar.sendError(ctx, err)
		return
	}

	// Convert response to AgentMessage
	agentMsg := &proto.AgentMessage{
		Id:     response.ID,
		Type:   proto.MessageType_MESSAGE_TYPE_RESPONSE,
		Sender: ar.BaseRole.GetRole(),
		Content: &proto.AgentMessage_Message{
			Message: response.Content,
		},
	}

	ctx.Send(ctx.Parent(), agentMsg)
}

// sendError sends an error message to the parent actor
func (ar *ActorRole) sendError(ctx protoactor.Context, err error) {
	errMsg := &proto.AgentMessage{
		Id:   uuid.NewString(),
		Type: proto.MessageType_MESSAGE_TYPE_ERROR,
		Content: &proto.AgentMessage_Error{
			Error: &proto.Error{
				Code:    "INTERNAL_ERROR",
				Message: err.Error(),
			},
		},
		Sender: ar.BaseRole.GetRole(),
	}
	ctx.Send(ctx.Parent(), errMsg)
}

// Think combines thinking capabilities of both actor and role
func (ar *ActorRole) Think(ctx context.Context, input string) (*actor.Thought, error) {
	thought, err := ar.BaseActor.Think(ctx, input)
	if err != nil {
		return nil, err
	}

	// Store thought in role's memory
	if err := ar.rc.memory.Store(ctx, &memory.MemoryEntry{
		ID:      uuid.NewString(),
		Type:    "thought",
		Content: thought.Content,
		Metadata: map[string]interface{}{
			"reasoning":  thought.Reasoning,
			"plan":       thought.Plan,
			"critical":   thought.Critical,
			"nextAction": thought.NextAction,
		},
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}); err != nil {
		return nil, fmt.Errorf("failed to store thought: %w", err)
	}

	return thought, nil
}

// SetLLM sets the LLM provider for both actor and role
func (ar *ActorRole) SetLLM(llm providers.LLMProvider) {
	ar.BaseActor.SetLLM(llm)
	ar.BaseRole.llm = llm
}

// SetKnowledgeBase sets the knowledge base for both actor and role
func (ar *ActorRole) SetKnowledgeBase(kb *knowledge.RAGEngine) {
	ar.BaseActor.SetKnowledgeBase(kb)
}

// AddAction adds an action to both actor and role
func (ar *ActorRole) AddAction(action actor.IAction) {
	ar.BaseActor.AddAction(action)
	ar.BaseRole.AddAction(action)
}

// AddCapability adds a capability to both actor and role
func (ar *ActorRole) AddCapability(capability string) {
	ar.BaseActor.AddCapability(capability)
	ar.BaseRole.AddCapability(capability)
}

// GetCapabilities returns combined capabilities
func (ar *ActorRole) GetCapabilities() []string {
	actorCaps := ar.BaseActor.GetCapabilities()
	roleCaps := ar.BaseRole.GetCapabilities()

	// Combine and deduplicate capabilities
	capMap := make(map[string]bool)
	for _, cap := range actorCaps {
		capMap[cap] = true
	}
	for _, cap := range roleCaps {
		capMap[cap] = true
	}

	combined := make([]string, 0, len(capMap))
	for cap := range capMap {
		combined = append(combined, cap)
	}
	return combined
}

// SetSupervisor sets the actor's supervisor
func (ar *ActorRole) SetSupervisor(supervisor *protoactor.PID) {
	ar.supervisor = supervisor
}

// GetSupervisor returns the actor's supervisor
func (ar *ActorRole) GetSupervisor() *protoactor.PID {
	return ar.supervisor
}
